﻿namespace Jerky.Core;

/// <summary>
/// Jerky引擎
/// </summary>
/// <remarks>进行书签替换操作</remarks>
/// <example>
/// <code>
/// var path = "path/to/your docx file";
/// var engine = new JerkyEngine(path);
/// 
/// var dict = new Dictionary&lt;string,string&gt;
/// {
///     ["XAUAT"] = "西建大",
///     ["MIFANS"] = "米粉"
/// };
/// E
/// var replacedTags = engine.ReplaceBookmarksAsync(dict).Result;
/// // var replacedTags = await engine.ReplaceBookmarksAsync(dict);
/// Console.WriteLine($"操作完成，共替换{replacedTags}处书签");
/// </code>
/// </example>
public class JerkyEngine
{
    #region 字段

    private string? _path;

    private bool _trimNumber;

    #endregion

    #region 属性

    /// <summary>
    /// 是否去除数字
    /// </summary>
    /// <remarks>若出现模板中多个书签对应同一个值时，可以将此值设为True,并且书签后可加数字用于区分，替换时将忽略书签后的数字</remarks>
    public bool TrimNumber { get => _trimNumber; set => _trimNumber = value; }

    #endregion

    #region 公共方法

    /// <summary>
    /// 构造方法
    /// </summary>
    /// <param name="path">文档路径</param>
    public JerkyEngine(string path)
    {
        _path = path;
    }

    /// <summary>
    /// 替换书签
    /// </summary>
    /// <param name="bookmarkReplacements">书签字典&lt;书签名称,书签值(字符串替换)&gt;</param>
    /// <returns></returns>
    /// <exception cref="FileNotFoundException">模板文件不存在</exception>
    public async Task<int> ReplaceBookmarksAsync(IDictionary<string, object> bookmarkReplacements)
    {
        ArgumentNullException.ThrowIfNull(nameof(bookmarkReplacements));

        if (!File.Exists(_path))
        {
            throw new FileNotFoundException("文件不存在", _path);
        }

        return await ReplaceBookmarksInnerAsync(bookmarkReplacements);
    }

    #endregion

    #region 私有方法

    private async Task<int> ReplaceBookmarksInnerAsync(IDictionary<string, object> bookmarkReplacements)
    {
        var tempFilePath = Path.GetTempFileName();
#pragma warning disable CS8604 // 引用类型参数可能为 null。
        File.Copy(_path, tempFilePath, true);
#pragma warning restore CS8604 // 引用类型参数可能为 null。

        using var package = ZipHelper.OpenPackage(tempFilePath, FileMode.Open);

        var documentPart = ZipHelper.GetPackagePart(package, "/word/document.xml");
        var documentContent = await ZipHelper.ReadPackagePartContentAsync(documentPart);

        var document = XDocument.Parse(documentContent);

        XNamespace w = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";

        var count = 0;
        foreach (var bookmark in bookmarkReplacements)
        {
            try
            {
#pragma warning disable CS8602 // 解引用可能出现空引用。
                var bookmarkStarts = this.TrimNumber ?
                    XMLHelper.FindElements(document, w + "bookmarkStart").Where(b => b.Attribute(w + "name").Value.RemoveTrailingDigits() == bookmark.Key) :
                    XMLHelper.FindElements(document, w + "bookmarkStart").Where(b => b.Attribute(w + "name").Value == bookmark.Key);

                foreach (var bookmarkStart in bookmarkStarts)
                {
                    var bookmarkId = bookmarkStart.Attribute(w + "id").Value;

                    var bookmarkEnd = XMLHelper.FindElements(document, w + "bookmarkEnd")
                                               .FirstOrDefault(b => b.Attribute(w + "id").Value == bookmarkId);

                    if (bookmarkEnd != null)
                    {
                        var elementsBetween = bookmarkStart.ElementsAfterSelf().TakeWhile(e => e != bookmarkEnd).ToList();
                        foreach (var element in elementsBetween)
                        {
                            element.Remove();
                        }

                        bookmarkStart.AddAfterSelf(new XElement(w + "r", new XElement(w + "t", bookmark.Value)));
                        count++;
                    }
                }
#pragma warning restore CS8602 // 解引用可能出现空引用。
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
        }

        var updatedContent = document.ToString();
        await ZipHelper.WritePackagePartContentAsync(documentPart, updatedContent);

        ZipHelper.ClosePackage(package);
        File.Copy(tempFilePath, _path, true);
        File.Delete(tempFilePath);

        return count;
    }

    #endregion
}
